feat(vrt): add --config flag to qc vrt#9
Conversation
There was a problem hiding this comment.
PR #9 Review — feat(vrt): add --config flag to qc vrt
Scope note: The title is narrow but the diff is broad — it touches CI workflows,
package.json/lockfile,tsconfig, command files (app,backup,crawler,env,vrt), utils (api,config), login, and tests. All areas are covered below.
Blockers
1. package.json:1 version downgraded — will break npm publish
- "version": "0.4.3", ← origin/main (base)
+ "version": "0.4.0", ← HEAD
The base branch is at 0.4.3; this PR regresses it to 0.4.0. Any tag-triggered publish will push a lower version that npm will reject as already-published (or silently shadow a prior release). Looks like a rebase artefact — restore to 0.4.3 (or bump to 0.4.4/0.5.0 as appropriate).
2. .github/workflows/publish.yml — OIDC trusted publishing removed, replaced with long-lived static token
base publish.yml:7-9:
permissions:
id-token: write # required for npm OIDC trusted publishingOn HEAD the permissions block is gone entirely, and the publish step now uses NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}.
OIDC provenance tokens are scoped to a single workflow run and cannot be exfiltrated; a static NPM_TOKEN is a long-lived credential that, if leaked from the secrets store, grants permanent publish access. Restore the permissions: id-token: write block and the --provenance publish flag.
3. __tests__/utils/api.test.ts — 395 lines of behavioural tests deleted (524 → 129 lines)
Base: 524 lines covering getApplications, getEnvironments, getMetrics, getLogs, getProjects, getCrawlers, getBackups, the constructor, and 401/403/404 friendly-message paths. HEAD: 129 lines of structural stubs of the form expect(typeof client.getX).toBe('function').
The inline comment says these are "better suited as E2E tests", but the deleted tests mocked the SDK successfully and exercised error-handling branches E2E tests typically don't cover. Deleting them without replacement leaves the error-handling paths untested. Restore the behavioural tests with updated mocks (matching the refactored constructor), or add equivalent coverage before merging.
Warnings
W1. package.json:13 — dev script changed from tsx to ts-node --esm
- "dev": "tsx src/index.ts", ← origin/main
+ "dev": "ts-node --esm src/index.ts", ← HEAD
ts-node is present in HEAD devDependencies (^10.9.1), so npm run dev will still run. However tsx (also in devDeps) handles ESM/CJS interop more reliably for modern TS projects. The regression looks unintentional — confirm it is deliberate.
W2. src/commands/vrt.ts:524-526 — parseBasicAuth returns undefined password for token-only input
function parseBasicAuth(auth: string): { username: string; password: string } {
const [username, password] = auth.split(':');
return { username, password };
}--quant-auth mytoken (no colon) yields password === undefined at runtime despite the declared string type; Playwright httpCredentials then receives { username: 'mytoken', password: undefined }. Add a guard:
if (!auth.includes(':')) throw new Error('--quant-auth / --remote-auth must be in user:pass format');W3. src/commands/vrt.ts:111-113 — parseFloat/parseInt with no NaN guard
const threshold = parseFloat(options.threshold || config.threshold?.toString() || '0.01');
const maxPages = parseInt(options.maxPages || ..., 10);
const maxDepth = parseInt(options.maxDepth || ..., 10);parseFloat('abc') → NaN and downstream comparisons silently misbehave. Add Number.isNaN checks and surface a clear error.
W4. publish.yml / test.yml — action versions downgraded v6 → v4
Base uses actions/checkout@v6 and actions/setup-node@v6 (Node 24); HEAD downgrades both to @v4 (Node 18). v6 of these actions does exist, so this is a deliberate-looking downgrade, not a typo fix. Confirm the Node 24 → 18 change matches the project's supported engine range. Regardless of version, SHA-pinning the actions is recommended for supply-chain hardening.
W5. src/utils/config.ts:239 — parameter path shadows the path module import
// config.ts:3
import { join, resolve } from 'path';
// config.ts:239
export async function loadVRTConfigFrom(path: string): Promise<VRTConfig | null> {The parameter shadows the module import — a readability hazard that will trip no-shadow lint rules. Rename to filePath. (Note: relative-path resolution is fine — resolveVRTConfig already calls resolve(cwd, configPath) before invoking this function.)
W6. src/commands/env.ts:700 — as any cast for SDK type workaround
// SDK incorrectly types this as void, but it actually returns data
const data = response.data as any;The comment is honest, but as any disables downstream type checking. Prefer a narrower assertion (as { body?: unknown }) and link the upstream SDK issue so the workaround can be removed later.
Test coverage gaps
- Behavioural
ApiClienterror-handling tests deleted without replacement (see Blocker 3). parseBasicAuthhas no tests — add cases for no colon, multiple colons (password containing:), empty string.- No integration test exercising
handleVRTwithoptions.configset (the actual--configCLI path). - No tests asserting graceful rejection of non-numeric
threshold/maxPages/maxDepth.
Nits (non-blocking)
vrt.tshelp text for--quant-auth/--remote-authcould state theuser:passformat so users don't discover it at runtime. Note also that credentials passed as CLI flags appear inps/shell history/CI logs — the config-file path is the safer place for them.tsconfiglib: ES2020cleanup (droppingDOM) is a sensible tightening — no action needed.
Findings withdrawn during verification (for transparency)
VRTConfig.projectsis typed required (config.ts:21), soObject.keys(config.projects)cannot throw on a valid config — no bug.AppEnvironment/AppDatainterfaces are still present in HEADapp.ts— not removed.- Relative
--configpaths are resolved correctly byresolveVRTConfig— no bug.
Verdict: REQUEST CHANGES
Three blockers must be resolved before merge: the version regression in package.json, the removal of OIDC trusted publishing in favour of a static token, and the deletion of 395 lines of behavioural API tests without replacement. The remaining items are warnings, coverage gaps, and nits.
Allow `qc vrt --config <path>` to read a specific VRT config file instead of always using ~/.quant/vrt-config.json. Enables generating the config from an external source (e.g. a Pulumi stack output) without clobbering the user's personal config. Adds loadVRTConfigFrom(path) and a testable resolveVRTConfig() selector; an explicit --config that is missing/invalid errors loudly (no silent fallback). README updated; unit tests added (no Chromium/network). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
9dc36ce to
1293773
Compare
|
Thanks for the thorough review. Root cause of the three blockers: this branch was accidentally cut from a stale local Blockers — all resolved by the rebase (none were changes from this PR)
The CI action-version / Node-runtime "downgrade" was the same stale-base artifact and is likewise gone. Warnings
Verification on the rebased branch
|
What
Adds
qc vrt --config <path>so VRT can read a specific config file instead of always using~/.quant/vrt-config.json. This lets the config be generated from an external source (e.g. thepulumi-govcmsvrtConfigstack output) without clobbering a user's personal config.pulumi stack output vrtConfig --json --show-secrets > /tmp/vrt.json qc vrt --config /tmp/vrt.json --format jsonChanges
loadVRTConfigFrom(path)— load/parse a VRT config from an explicit path (null-on-failure, mirrorsloadVRTConfig).loadVRTConfig()is nowloadVRTConfigFrom(VRT_CONFIG_FILE).resolveVRTConfig()— pure, injectable config-source selector (resolves relative--configagainst cwd). Unit-testable without launching Chromium.--config <path>flag +VRTOptions.config; an explicit path that is missing/invalid errors loudly (no silent fallback to the default).Tests
New unit suites (
__tests__/utils/vrt-config.test.ts,vrt-resolve-config.test.ts) — 7 tests, no Chromium/network. Unit run green (41/41).🤖 Generated with Claude Code